Skip to content

feat: sourcemap upload parity flags and issue archive command#883

Closed
BYK wants to merge 1 commit intomainfrom
feat/sourcemap-parity-and-issue-archive
Closed

feat: sourcemap upload parity flags and issue archive command#883
BYK wants to merge 1 commit intomainfrom
feat/sourcemap-parity-and-issue-archive

Conversation

@BYK
Copy link
Copy Markdown
Member

@BYK BYK commented Apr 29, 2026

Summary

Closes #600 — implements Phase 1 of the feature parity plan (sourcemap enhancements + issue archive).

Sourcemap upload: 6 new flags for old sentry-cli parity

  • --dist — Distribution identifier to disambiguate builds within a release. Passed through to the artifact bundle manifest and assemble endpoint.
  • --ext — Custom file extensions on upload (was only available on inject). Forwards to the discovery/inject flow.
  • --no-rewrite — Upload files as-is without injecting debug IDs, relying on release/URL-based matching instead.
  • --ignore / --ignore-file — Gitignore-style file exclusion patterns on both inject and upload. Uses the ignore npm package (already in devDependencies).
  • --strip-prefix — Remove an explicit prefix from uploaded file URLs.
  • --strip-common-prefix — Auto-detect and strip the longest common directory prefix from all files.

Sourcemap inject: sourceMappingURL directive following

The inject command's companion map discovery was previously strictly filename-based (foo.jsfoo.js.map). Now it also parses //# sourceMappingURL=<url> directives from JS files:

  • Reads only the last 512 bytes of each file for efficiency
  • Resolution order: convention naming first, then sourceMappingURL fallback
  • Gracefully skips data: (inline) and http:/https: (remote) URLs with debug logging

New command: sentry issue archive (alias: ignore)

Archives (ignores) an issue, suppressing alerts until an optional condition is met:

  • --duration — Ignore for N minutes
  • --count / --window — Ignore until N events in M minutes
  • --users / --user-window — Ignore until N users affected in M minutes

Maps to the existing updateIssueStatus("ignored") API. Registered with ignore as an alias.

Tests

  • 11 new tests covering all new sourcemap flags and sourceMappingURL following
  • All 6,239 existing unit tests pass
  • Typecheck clean, lint clean

Add missing sourcemap flags for old sentry-cli parity:
- --dist: distribution identifier for builds within a release
- --ext: custom file extensions (passthrough from inject to upload)
- --no-rewrite: upload files without injecting debug IDs
- --ignore/--ignore-file: gitignore-style file exclusion
- --strip-prefix/--strip-common-prefix: path prefix removal

Add sourceMappingURL directive following in inject:
- Reads last 512 bytes of JS files to find //# sourceMappingURL=
- Resolves relative paths when convention naming (foo.js.map) fails
- Gracefully skips data: and http: URLs

Add issue archive command (sentry issue archive, alias: ignore):
- Maps to Sentry 'ignored' status via existing updateIssueStatus()
- Supports --duration, --count, --window, --users, --user-window
  for conditional archiving

Closes #600 (sourcemap and issue mutation gaps)
@github-actions
Copy link
Copy Markdown
Contributor

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://cli.sentry.dev/_preview/pr-883/

Built to branch gh-pages at 2026-04-29 05:25 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@github-actions
Copy link
Copy Markdown
Contributor

Codecov Results 📊

6311 passed | Total: 6311 | Pass Rate: 100% | Execution Time: 0ms

📊 Comparison with Base Branch

Metric Change
Total Tests 📈 +11
Passed Tests 📈 +11
Failed Tests
Skipped Tests

All tests are passing successfully.

❌ Patch coverage is 75.53%. Project has 13197 uncovered lines.
❌ Project coverage is 75.91%. Comparing base (base) to head (head).

Files with missing lines (6)
File Patch % Lines
src/commands/issue/archive.ts 60.87% ⚠️ 36 Missing
src/commands/sourcemap/upload.ts 84.50% ⚠️ 20 Missing
src/lib/sourcemap/inject.ts 81.48% ⚠️ 20 Missing
src/lib/api/sourcemaps.ts 38.46% ⚠️ 8 Missing
src/commands/sourcemap/inject.ts 75.86% ⚠️ 7 Missing
src/lib/api/issues.ts 0.00% ⚠️ 1 Missing
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
- Coverage    75.97%    75.91%    -0.06%
==========================================
  Files          294       295        +1
  Lines        54402     54785      +383
  Branches         0         0         —
==========================================
+ Hits         41329     41588      +259
- Misses       13073     13197      +124
- Partials         0         0         —

Generated by Codecov Action

Comment on lines 155 to 163
const diag = await diagnoseEmptyDiscovery(dir, { extensions });
throw buildEmptyDiscoveryError(dir, diag);
}

const results = await injectDirectory(dir, {
extensions,
ignorePatterns,
dryRun: flags["dry-run"],
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The inject command fails to pass the ignoreMatcher to injectDirectory, causing files specified in --ignore-file to be incorrectly processed during injection.
Severity: MEDIUM

Suggested Fix

Pass the fully constructed ignoreMatcher from the inject command to the injectDirectory function via the options.ignoreMatcher property, instead of only passing ignorePatterns.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/commands/sourcemap/inject.ts#L155-L163

Potential issue: The `inject` command correctly builds an `ignoreMatcher` using patterns
from both the `--ignore` flag and the `--ignore-file` flag. This matcher is used for an
initial file discovery. However, when calling the `injectDirectory` function, only the
`ignorePatterns` from the `--ignore` flag are passed. The `injectDirectory` function
then rebuilds the ignore matcher without the contents of the file specified by
`--ignore-file`. As a result, files that should be ignored via the ignore file are
incorrectly processed and have debug IDs injected.

Did we get this right? 👍 / 👎 to inform future reviews.

*/
async function findCompanionMap(jsPath: string): Promise<string | undefined> {
const mapPath = `${jsPath}.map`;
const SOURCE_MAPPING_URL_RE = /\/\/[#@]\s*sourceMappingURL\s*=\s*(\S+)\s*$/m;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The regex for finding sourceMappingURL uses a multiline flag that can cause it to match the first occurrence in a file's tail, not the last, potentially picking up incorrect URLs from string literals.
Severity: LOW

Suggested Fix

Modify the SOURCE_MAPPING_URL_RE to ensure it only matches the final sourceMappingURL directive. This can be done by removing the multiline (m) flag so $ only matches the end of the string, or by using the global (g) flag and taking the last result.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/lib/sourcemap/inject.ts#L95

Potential issue: The regex `SOURCE_MAPPING_URL_RE` is intended to find the source
mapping URL comment, which should be the last line in a file. However, it uses the
multiline (`m`) flag, causing `$` to match the end of any line. When combined with
`String.match()`, which returns the first match, it can incorrectly extract a URL from a
string literal if it appears before the actual final `sourceMappingURL` comment in the
last 512 bytes of a file. This violates the documented intent to match only the final
directive.

Did we get this right? 👍 / 👎 to inform future reviews.

Comment on lines +287 to +293
let pathPrefixToStrip = flags["strip-prefix"] ?? "";
if (flags["strip-common-prefix"]) {
const allRelative = results.flatMap(({ jsPath, mapPath }) => [
relative(resolvedDir, jsPath).replaceAll("\\", "/"),
relative(resolvedDir, mapPath).replaceAll("\\", "/"),
]);
pathPrefixToStrip = computeCommonPrefix(allRelative);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: When both --strip-prefix and --strip-common-prefix flags are used, the value from --strip-prefix is silently overwritten, leading to unexpected behavior.
Severity: MEDIUM

Suggested Fix

Implement logic to handle the case where both flags are provided. Either throw a validation error to indicate they are mutually exclusive, or establish a clear precedence (e.g., strip-prefix should override strip-common-prefix) and document this behavior.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/commands/sourcemap/upload.ts#L287-L293

Potential issue: When both the `--strip-prefix` and `--strip-common-prefix` flags are
used with the `upload` command, the value from `--strip-prefix` is silently overwritten
by the value computed for `--strip-common-prefix`. There is no validation to prevent
using both flags, nor is there a warning that the explicitly provided prefix is being
ignored. This undocumented behavior can lead to unexpected artifact paths in Sentry, as
the user's explicit configuration is discarded without their knowledge.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0970403. Configure here.


const results = await injectDirectory(dir, {
extensions,
ignorePatterns,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inject command drops --ignore-file patterns during injection

High Severity

The inject command builds an ignoreMatcher from both --ignore patterns and --ignore-file, and correctly uses it for the discoverFilePairs pre-check. But it then passes only ignorePatterns (not ignoreMatcher) to injectDirectory. Inside injectDirectory, the matcher is rebuilt from just ignorePatterns, losing all patterns from --ignore-file. This makes --ignore-file silently ineffective during actual injection. The upload command correctly passes ignoreMatcher to injectDirectory; the inject command needs to do the same.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0970403. Configure here.

try {
const tail = await readFileTail(jsPath);
const match = tail.match(SOURCE_MAPPING_URL_RE);
return match?.[1];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regex returns first sourceMappingURL match, not last

Low Severity

The source map spec says the last sourceMappingURL directive in a file is authoritative. extractSourceMappingUrl uses tail.match(SOURCE_MAPPING_URL_RE) which returns the first match in the 512-byte tail. If the tail contains multiple directives (e.g. concatenated bundles), the wrong sourcemap could be paired. The regex needs the g flag with iteration to find the last match, or a reverse search approach.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0970403. Configure here.

@BYK
Copy link
Copy Markdown
Member Author

BYK commented Apr 30, 2026

Splitting into two PRs: one for sourcemap enhancements, one for issue archive.

@BYK BYK closed this Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Catch up with old sentry-cli

1 participant